package com.tianji.learning.service.impl;

import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.leanring.LearningLessonDTO;
import com.tianji.api.dto.leanring.LearningRecordDTO;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.AssertUtils;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.ObjectUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.LearningRecordFormDTO;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.SectionType;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.mq.message.PointsMessage;
import com.tianji.learning.service.ILearningLessonService;
import com.tianji.learning.service.ILearningRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.learning.utils.LearningRecordDelayTaskHandler;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

import static com.tianji.learning.enums.SectionType.VIDEO;

/**
 * <p>
 * 学习记录表 服务实现类
 * </p>
 *
 * @author fx
 * @since 2024-08-22
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LearningRecordServiceImpl extends ServiceImpl<LearningRecordMapper, LearningRecord> implements ILearningRecordService {
    private final ILearningLessonService lessonService;
    private final CourseClient courseClient;
    private final LearningRecordDelayTaskHandler taskHandler;
    private final RabbitMqHelper mqHelper;

    /**
     * 01 查询当前用户指定课程的学习进度 (day03-随堂-01)
     *
     * @param courseId 课程id
     * @return 课表信息、学习记录及进度信息
     */
    @Override
    public LearningLessonDTO queryLearningRecordByCourse(Long courseId) {
        //1 获取当前用户id
        Long userId = UserContext.getUser();
        //2 查询课表,获取LessonId
        LearningLesson lesson = lessonService.queryLessonByUserIdAndCourseId(userId, courseId);
        if (lesson == null) {
            return null;
        }
        //3 根据lessonId查询学习记录
        List<LearningRecord> list = this.lambdaQuery().eq(LearningRecord::getLessonId, lesson.getId()).list();
        //4 封装结果
       /* LearningLessonDTO dto = new LearningLessonDTO();
        dto.setId(lesson.getId());
        dto.setLatestSectionId(lesson.getLatestSectionId());
        dto.setRecords(BeanUtils.copyList(list, LearningRecordDTO.class));
        return dto;*/
        return LearningLessonDTO.of(lesson.getId(), lesson.getLatestSectionId(), BeanUtils.copyList(list, LearningRecordDTO.class));
    }

    /**
     * 02 新增学习记录 (day03-随堂-02)（day04-随堂-01）
     *
     * @param formDTO
     */
    @Override
    @Transactional
    public void addLearningRecord(LearningRecordFormDTO formDTO) {
        // 1 处理学习记录类型 判断是exam还是video
        boolean firstFinished = formDTO.getSectionType() == VIDEO ? handleVideoRecord(formDTO) : handleExamRecord(formDTO);
        // 2 处理课表数据
        if (!firstFinished && formDTO.getDuration() == -1) {
            return;
        }
        handleLearingLesson(formDTO, firstFinished);
    }

    /**
     * tool-01 处理 视频学习记录
     *
     * @param formDTO
     * @return
     */
    private boolean handleVideoRecord(LearningRecordFormDTO formDTO) {
        //1 根据courseId和sectionId查询 旧学习记录
        // 优化方案 -先从redis中查，若存在，则直接返回。不存在，则从数据库中查，并存如redis中

        /*  LearningRecord oldRecord = this.lambdaQuery()
                .eq(LearningRecord::getLessonId, formDTO.getLessonId())
                .eq(LearningRecord::getSectionId, formDTO.getSectionId())
                .one();*/
        LearningRecord oldRecord = this.queryOldRecord(formDTO.getLessonId(), formDTO.getSectionId());

        //2 判断学习记录是否存在 第一次添加学习记录
        if (ObjectUtils.isEmpty(oldRecord)) {
            //2.1 不存在，新增学习记录
            LearningRecord record = BeanUtils.copyBean(formDTO, LearningRecord.class);
            //2.2 填充缺失数据
            record.setUserId(UserContext.getUser());
            //2.3 写入数据库
            log.info("第一次新增学习记录");
            boolean save = this.save(record);
            if (!save) {
                throw new DbException("新增学习记录失败");
            }
            //2.4 返回结果  第一次添加学习记录
            return false;
        }

        //3 存在，更新学习记录
        //3.1 判断是否是第一次完成当前小结
        boolean firstFinished = !oldRecord.getFinished() && formDTO.getMoment() << 1 >= formDTO.getDuration();

        //3.2 如果非第一次学完，也不是第一次添加学习记录 更新学习记录到redis，并发送一个延时任务
        if (!firstFinished) {
            LearningRecord learningRecord = new LearningRecord();
            learningRecord.setLessonId(formDTO.getLessonId());
            learningRecord.setSectionId(formDTO.getSectionId());
            learningRecord.setMoment(formDTO.getMoment());
            learningRecord.setId(oldRecord.getId());
            learningRecord.setFinished(oldRecord.getFinished());

            formDTO.setDuration(-1);//方便调用这判断 非第一次完成，非第一次添加学习记录的情况（刚开始学习）
            //3.2.1 更新学习记录到redis，并发送一个延时任务
            taskHandler.addLearningRecordDelayCheckTask(learningRecord);
            return false;
        }

        //3.2 如果是第一次学完， firstFinished=true 更新mysql中学习记录，清空redis
        log.info("第一次完成当前小结");
        boolean updateResult = this.lambdaUpdate()
                .set(LearningRecord::getMoment, formDTO.getMoment())
                .set(LearningRecord::getFinished, true)
                .set(LearningRecord::getFinishTime, formDTO.getCommitTime())
                .eq(LearningRecord::getId, oldRecord.getId())
                .update();
        if (!updateResult) {
            throw new DbException("新增学习记录失败");
        }
        //清空redis缓存（目的是保证redis双写一致性）
        taskHandler.clearLearningRecord(formDTO.getLessonId(), formDTO.getSectionId());
        // 5 TODO mq发送消息->积分系统，保存视频学习积分记录
        try {
            mqHelper.send(
                    MqConstants.Exchange.LEARNING_EXCHANGE,
                    MqConstants.Key.LEARN_SECTION,
                    PointsMessage.of(UserContext.getUser(),10)
            );
        } catch (Exception e) {
            log.error("保存学习视频积分失败，错误原因：{}",e);
        }
        return true;
    }

    /**
     * tool-02 处理 考试记录
     *
     * @param formDTO
     * @return
     */
    private boolean handleExamRecord(LearningRecordFormDTO formDTO) {
        //1 转换Dto为po
        LearningRecord learningRecord = BeanUtils.copyBean(formDTO, LearningRecord.class);
        //2 填充缺失数据
        learningRecord.setUserId(UserContext.getUser());
        learningRecord.setFinished(true);
        learningRecord.setFinishTime(formDTO.getCommitTime());
        //3 写入数据库
        boolean save = this.save(learningRecord);
        if (!save) {
            throw new DbException("新增考试记录失败");
        }
        //4 返回结果
        return true;
    }

    /**
     * tool-03 处理 课表数据
     *
     * @param formDTO
     * @param firstFinished
     */
    private void handleLearingLesson(LearningRecordFormDTO formDTO, boolean firstFinished) {
        //1 查询课表数据
        LearningLesson lesson = this.lessonService.getById(formDTO.getLessonId());
        AssertUtils.isNotNull(lesson, "课表数据不存在，无法更新");

        //2 判断是否有新的完成的小结
        boolean allLearned = false;
        //2.1 查询课程数据
        if (firstFinished) {
            CourseFullInfoDTO courseInfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
            AssertUtils.isNotNull(courseInfo, "课程信息不存在，无法更新课表数据");

            //2.2 判断课程是否完全学完
            allLearned = lesson.getLearnedSections() + 1 >= courseInfo.getSectionNum();
        }
        //3 更新课程数据
        boolean updateResult = this.lessonService.lambdaUpdate()
                .set(LearningLesson::getLatestSectionId, formDTO.getSectionId())
                .set(LearningLesson::getLatestLearnTime, formDTO.getCommitTime())
                //.setSql(firstFinished, "learned_sections = learned_sections+1")
                .set(firstFinished, LearningLesson::getLearnedSections, lesson.getLearnedSections() + 1)
                .set(lesson.getStatus() == LessonStatus.NOT_BEGIN, LearningLesson::getStatus, LessonStatus.LEARNING)
                .set(allLearned, LearningLesson::getStatus, LessonStatus.FINISHED)
                .eq(LearningLesson::getId, lesson.getId())
                .update();
        if (!updateResult) {
            throw new DbException("更新课表数据失败");
        }

    }

    /**
     * tool-04 查询旧学习记录
     *
     * @param lessonId
     * @param sectionId
     * @return
     */
    private LearningRecord queryOldRecord(Long lessonId, Long sectionId) {
        //1 从缓存中查询
        LearningRecord oldRecord = taskHandler.readLearningRecordFromCache(lessonId, sectionId);
        //2 若存在，则直接返回
        if (ObjectUtils.isNotEmpty(oldRecord)) {
            return oldRecord;
        }

        //3 不存在，则从数据库中查，并存入redis中
        //3.1 select * from learning_record where lesson_id = #{lessonId} and section_id = #{sectionId}
        oldRecord = this.lambdaQuery()
                .eq(LearningRecord::getLessonId, lessonId)
                .eq(LearningRecord::getSectionId, sectionId).one();
        if (ObjectUtils.isEmpty(oldRecord)) {
            log.info("mysql中未查询到学习记录");
            return null;//不能报错，要作为后续判断是否为 第一次添加学习记录
        }
        taskHandler.writeLearningRecordToCache(oldRecord);

        //4 返回
        return oldRecord;
    }


}
